package jp.ac.aiit.spg.entity;

import java.io.Serializable;
import java.text.DecimalFormat;
import java.util.*;
import javax.persistence.*;

import jp.ac.aiit.spg.common.*;

@Entity
public class SpgTask implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;


	// version ***********************************
	@Version
	private Integer version;
	
	public Integer getVersion() {
		return version;
	}

	public void setVersion(Integer version) {
		this.version = version;
	}


	
	// id ***********************************
	@Id
	@GeneratedValue
	private Integer id;
	
	public Integer getId(){
		return this.id;
	}
	
	// 自らが属するWBS（1個） **********************
	@ManyToOne
	private SpgWBS wbs ;
	
	public SpgWBS getWbs(){
		return this.wbs;
	}

	public void setWbs(SpgWBS wbs) {
		// 関連の処理に責任を持つ
		this.wbs = wbs;
		wbs.addTask(this);
	}
	
	// 自らが属するToDo（1人用） **********************
	@ManyToOne
	private SpgToDo todo ;
	
	public SpgToDo getTodo(){
		return this.todo;
	}

	public void setTodo(SpgToDo todo) {
		// 関連の処理に責任を持つ
		this.todo = todo;
		todo.addTask(this);
	}
	
	// WBS番号 ******************************
	@Column(nullable=false)
	private String wbsNumber;
	
	public String getWbsNumber() {
		return wbsNumber;
	}

	public void setWbsNumber(String wbsNumber) {
		this.wbsNumber = wbsNumber;
	}

	/*
	 * 自分のWBS番号を求める
	 */
	public void calcWbsNumber() {
		
		LinkedList<String> wbsNumberStructure = new LinkedList<String>();
		SpgTask currentTask;
		String currentWbsNum;
		String zero = "0";	// 緊急避難的措置
		
		currentTask = this;
		currentWbsNum = this.getWbsNumber();
		while ( ! zero.equals(currentWbsNum)  ) {
			wbsNumberStructure.addFirst(currentTask.calcMyWbsIndex(currentTask));
			currentTask = currentTask.getParent();
			currentWbsNum = currentTask.getWbsNumber();
		}

		// WBS番号をフォーマットする
		StringBuffer sbuf = new StringBuffer();
		for (String s : wbsNumberStructure) {
			sbuf.append(s);
			sbuf.append(".");
		}
		String s = sbuf.substring(0, (sbuf.length()-1));

		// WBS番号をセットする
		this.setWbsNumber(s);
		
	}
	
	/*
	 * 自分の子タスクのWBS番号を変更する
	 * （自分のWBS番号が変更された場合に使用する）
	 */
	public void reCalcWbsNumber() {
		
		if ( this.children.size() == 0 ) {
			return;
		} else {
			for (SpgTask t : this.children ) {
				t.calcWbsNumber();
				t.reCalcWbsNumber();
			}
		}
		
	}
	
	/*
	 * 親タスクの「子タスクリスト」の中における自分の順序（番号）を求める
	 */
	private String calcMyWbsIndex(SpgTask st) {
		
		if (st.getParent() != null) {
			int n = this.getParent().getChildren().indexOf(this) + 1;
			DecimalFormat wbsNumberFormat = new DecimalFormat("00"); // 1-99まで！
			String s = wbsNumberFormat.format(n);			
			return s;			
		} else {
			return null;
		}
	}
	
	
	// タスク名（重複可能） *********************************
	@Column(nullable=false)
	private String name;
	
	public String getName(){
		return this.name;
	}
	
	public void setName(String name){
		this.name = name;
	}


	// 作業時間 *********************************
	private Integer workingTime;

	public Integer getWorkingTime() {
		return workingTime;
	}

	public void setWorkingTime(Integer workingTime) {
		this.workingTime = workingTime;
	}

	
	/*
	 * 自分の上位タスクの作業時間を変更する
	 * （自分の作業時間が設定/変更/自分が削除/された場合に使用する）
	 */
	public void reCalcWorkingTime() {
	
		if ( this.parent == null ) {
			return;
			
		} else {
			
			// 親タスクの作業時間を計算する
			int sum = 0;
			for (SpgTask t : this.parent.children ) {
				sum = sum + t.getWorkingTime();
			}
			this.parent.setWorkingTime(sum);
			
			// 親タスクの同じメソッドを呼び出す（再帰処理）
			this.parent.reCalcWorkingTime();
			
		}
		
		
	}
	
	
	// 作業開始予定日時を手入力したか否か ********
	private boolean isStartTimeSpecifiedByUser;
	
	public boolean isStartTimeSpecifiedByUser() {
		return isStartTimeSpecifiedByUser;
	}

	public void setStartTimeSpecifiedByUser(boolean isStartTimeSpecifiedByUser) {
		this.isStartTimeSpecifiedByUser = isStartTimeSpecifiedByUser;
	}
	
	
		
	// 作業開始予定日時 *********************************
	@Temporal(TemporalType.TIMESTAMP)
	private Date startTimePlanned;

	public Date getStartTimePlanned() {
		return startTimePlanned;
	}

	public void setStartTimePlanned(Date startTimePlanned) {
		this.startTimePlanned = startTimePlanned;
	}

	
	// 作業終了予定日時 *********************************
	@Temporal(TemporalType.TIMESTAMP)
	private Date finishTimePlanned;

	public Date getFinishTimePlanned() {
		return finishTimePlanned;
	}

	public void setFinishTimePlanned(Date finishTimePlanned) {
		this.finishTimePlanned = finishTimePlanned;
	}

	/*
	 * 自分の作業終了予定日時を計算する
	 * （自分の作業時間が設定/変更された場合に使用する）
	 * 但し現在はダミーのロジック
	 */
	public void calcFinishTimePlanned(){
		
		// 一日4時間の固定値で計算
		int DAILYWORKINGHOUR = 4;
		int howManyDaysRequired = this.getWorkingTime() / DAILYWORKINGHOUR ;
		int howManyHoursRequired = this.getWorkingTime() % DAILYWORKINGHOUR ;
		
		Calendar cal = Calendar.getInstance();
		cal.setTime(this.startTimePlanned);
		cal.add(Calendar.DAY_OF_YEAR, howManyDaysRequired);
		cal.add(Calendar.HOUR_OF_DAY, howManyHoursRequired);
		
		Date result = cal.getTime();
		this.setFinishTimePlanned(result);
		
	}

	/*
	 * 自分の上位タスクの完了予定日時を変更する
	 * （自分の作業時間が設定/変更された場合に使用する）
	 * 但し現在はダミーのロジック
	 * ！ロジックとしては全く不完全。スケジュールロジックの代わりにはならない。
	 */
	public void reCalcFinishTimePlanned() {

		if ( this.parent == null ) {
			
			return;
			
		} else {
			
			// 親タスクの開始予定日時が自分の開始予定日より遅いか、NULLであれば
			//  親タスクの開始予定日時に自分の開始予定日をセット
			//
			// 問題：子タスクの日程を遅くしても親タスクの開始は遅くならない。

			Calendar parentStart = Calendar.getInstance();
			if(this.parent.getStartTimePlanned() != null) {
				parentStart.setTime(this.parent.getStartTimePlanned());				
			}
			
			if (this.parent.getStartTimePlanned() == null ) {
			} else if ( this.parent.getStartTimePlanned().after(this.startTimePlanned )) {
				this.parent.setStartTimePlanned(this.startTimePlanned);			
			}
			
			// 親タスクの完了予定日時が自分の完了予定日より早いか、NULLであれば
			//  親タスクの完了予定日時に自分の完了予定日をセット
			//
			// 問題：子タスクを削除しても親タスクの完了が早まらない。
			//   あらかじめ親タスクの完了予定日がセットされており、かつそれが自分の
			//   完了予定日より遅い場合にこれが発生する。
			//   タスクの前後関係を考慮したロジックでないと、これは判断/対応できない。
			Calendar parentFinish = Calendar.getInstance();
			if(this.parent.getFinishTimePlanned() != null) {
				parentFinish.setTime(this.parent.getFinishTimePlanned());				
			}
			
			if (this.parent.getFinishTimePlanned() == null ) {
			} else if (this.parent.getFinishTimePlanned().before(this.finishTimePlanned)) {
				this.parent.setFinishTimePlanned(this.finishTimePlanned);			
			}

			
			// 親タスクの同じメソッドを呼び出す（再帰処理）
			this.parent.reCalcFinishTimePlanned();
			
		}

	}
	
	
	/*
	 * 自分の子タスクの完了予定日時を変更する
	 * 但し現在はダミーのロジックを呼んでいるため、あくまでも暫定措置。
	 * 
	 */
	public void reSchedule() {
		
		if ( this.children.size() == 0 ) {
			this.calcFinishTimePlanned();
			this.reCalcFinishTimePlanned();
		} else {
			for (SpgTask t : this.children ) {
				t.reSchedule();
			}
		}
		return ;

		
	}
	
	
	// 作業開始実績日時 *********************************
	@Temporal(TemporalType.TIMESTAMP)
	private Date startTimeActual;

	public Date getStartTimeActual() {
		return startTimeActual;
	}

	public void setStartTimeActual(Date startTimeActual) {
		this.startTimeActual = startTimeActual;
	}

	// 作業終了実績日時 *********************************
	@Temporal(TemporalType.TIMESTAMP)
	private Date finishTimeActual;

	public Date getFinishTimeActual() {
		return finishTimeActual;
	}

	public void setFinishTimeActual(Date finishTimeActual) {
		this.finishTimeActual = finishTimeActual;
	}

	
	// 割り当てられたメンバー（１名） **********************
	@ManyToOne
	private SpgMember member ;
	
	public SpgMember getMember(){
		return this.member;
	}

	public void setMember(SpgMember member) {
		// 関連の処理に責任を持つ
		this.member = member;
		member.addTask(this);
	}
	
	// 承認状態 *********************************
	@Enumerated
	private SpgAssignStatus assignment;
	
	
	public SpgAssignStatus getAssignment() {
		return assignment;
	}

	public void setAssignment(SpgAssignStatus assignment) {
		this.assignment = assignment;
	}


	// 進捗状況 *********************************
	@Enumerated
	private SpgProgressStatus progress;
	
	public SpgProgressStatus getProgress() {
		return progress;
	}

	public void setProgress(SpgProgressStatus progress) {
		this.progress = progress;
	}
	
	// 通信欄（拒否理由） ***************************
	private String assignMemo;
	
	public String getAssignMemo() {
		return assignMemo;
	}

	public void setAssignMemo(String assignmemo) {
		this.assignMemo = assignmemo;
	}

	// 成果物メモ ***************************
	@Lob
	private String deliverableMemo;
	
	public String getDeliverableMemo() {
		return deliverableMemo;
	}

	public void setDeliverableMemo(String deliverableMemo) {
		this.deliverableMemo = deliverableMemo;
	}

	// タスクの階層構造
	// WBS上の親タスク（１つ） *********************
	@ManyToOne
	SpgTask parent = null;
	
	public SpgTask getParent(){
		return this.parent;
	}
	
	public void setParent(SpgTask parent){
		// 関連の処理に責任を持つ
		this.parent = parent;
		parent.addChild(this);
	}
	
	// WBS上の子タスク（複数） *******************
	@OneToMany(mappedBy="parent")
	@OrderBy("wbsNumber ASC")
	private List<SpgTask> children = new LinkedList<SpgTask>();
	
	public List<SpgTask> getChildren(){
		return this.children;
	}

	public void setChildren(List<SpgTask> children) {
		this.children = children;
	}

	
	public void addChild(SpgTask st) {
		this.children.add(st);
	}
	
	public void removeChild(SpgTask st) {
		this.children.remove(st);
	}
	


	// タスクの前後関係
	// タスク・ネットワーク上の先行タスク（複数） *******************
	@ManyToMany(fetch = FetchType.EAGER)
	@OrderBy("wbsNumber ASC")
	private Set<SpgTask> predecessors = new HashSet<SpgTask>();
	
	public Set<SpgTask> getPredecessors(){
		return this.predecessors;
	}

	public void setPredecessors(Set<SpgTask> predecessors) {
		this.predecessors = predecessors;
	}
	
	public void addPredecessor(SpgTask st) {
		// 関連の処理に責任を持つ
		this.predecessors.add(st);
		st.addSuccessor(this);
	}
	
	public void removePredecessor(SpgTask st) {
		// 関連の処理に責任を持つ
		this.predecessors.remove(st);
		st.removeSuccessor(st);
	}
	
	// タスク・ネットワーク上の後続タスク（複数） *********************
	@ManyToMany(mappedBy="predecessors")
	@OrderBy("wbsNumber ASC")
	private Set<SpgTask> successors = new HashSet<SpgTask>();
	
	public Set<SpgTask> getSuccessors(){
		return this.successors;
	}

	public void setSuccessors(Set<SpgTask> successors) {
		this.successors = successors;
	}
	
	public void addSuccessor(SpgTask st) {
		this.successors.add(st);
	}
	
	public void removeSuccessor(SpgTask st) {
		this.successors.remove(st);
	}





	
}
